#include "Renderer.h"
#include "Constant.h"
#include "Types.h"
#include "Utils.h"
#include "texture.h"
#include "Surface.h"
#include "stdGeom.h"
Renderer::Renderer(void)
{
}
Renderer::~Renderer(void)
{
}
bool Renderer::Init(int screenW, int screenH)
{
	m_pDefFont = NULL;
	m_pScreen = NULL;
	m_pGLContext = NULL;

	//Default Fill mode settings
	m_fillMode = FM_Solid;
	m_gradientColors.push_back(color::Blue * 0.2f);
	m_pDrawTexture = NULL;
	m_bBillboarding = false;

	//Screen dimensions
	m_nScreenWidth = screenW;
	m_nScreenHeigth = screenH;

	//Initialize color gradient storage
	m_gradientColors.resize(1);

	cout << "Creating SDL window" << endl;
	//Create SDL window
	m_pScreen = SDL_CreateWindow("Constructor", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
		SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
	if(m_pScreen == NULL)
	{
		Error(cout << "Failed to create SDL window" << endl);
		return false;
	}

	//Request OpenGL 3.2 context
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
	//SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);

	//Enable Double buffering
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	//Enable verical sync
	int bSwapSet = SDL_GL_SetSwapInterval(1);

	m_pGLContext = SDL_GL_CreateContext(m_pScreen);
	if(m_pGLContext == NULL)
	{
		Error(cout << "Failed to create OpenGL 3.0 Context" << endl);
		return false;
	}

	//Retrieve context version
	SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &m_nGLContextMajor);
	SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &m_nGLContextMinor);
	cout << "Created OpenGL Context " << m_nGLContextMajor << "." << m_nGLContextMinor << endl;

	//Test OpenGL Context
	glMatrixMode(GL_PROJECTION);
	if(glErrorCheck())
	{
		CPrint(IRED, cout << "OpenGL " << m_nGLContextMajor << "." << m_nGLContextMinor << " Test failed trying default context");
		SDL_GL_DeleteContext(m_pGLContext);
		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
		m_pGLContext = SDL_GL_CreateContext(m_pScreen);
		if(m_pGLContext == NULL)
		{
			Error(cout << "Failed to create OpenGL Context" << endl);
			return false;
		}
		//Retrieve context version
		SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &m_nGLContextMajor);
		SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &m_nGLContextMinor);
		cout << "Created OpenGL Context " << m_nGLContextMajor << "." << m_nGLContextMinor << endl;
	}

	
	//Initialize GLEW
	GLenum glewError = glewInit();
	if(glewError != GLEW_NO_ERROR)
	{
		Error(cout << "Failed to initialize GLEW" << endl);
		Error(cout << glewGetErrorString(glewError) << endl);
	}

	//Initialize SDL_TTF (after renderer)
	int res = TTF_Init();
	if(res == -1)
	{
		Error(cout << "Failed to initialize TTF");
		return false;
	}

	if(!LoadFonts())
	{
		Error(cout << "Failed to load renderer fonts");
		return false;
	}

	if(!LoadStdModels())
	{
		Error(cout << "Failed to load gui geometry");
		return false;
	}
	if(!LoadShaders())
	{
		Error(cout << "Failed to load gui shaders");
		return false;
	}

	//Initialize image loading library SDL_Image
	ilInit();
	if(ilGetError() != IL_NO_ERROR)
	{
		Error(cout << "Failed to initialize DevIL");
		return false;
	}
	iluInit();
	if(ilGetError() != IL_NO_ERROR)
	{
		Error(cout << "Failed to initialize DevILU");
		return false;
	}

	//Set viewport
	glViewport(0, 0, m_nScreenWidth, m_nScreenHeigth);

	//Set clear color
	glClearColor(color::Black);
	glClearDepth(1.0f);

	//Load gui projection matrix
	//m_matProjection = matrix::Orthogonal(0, m_nScreenWidth, 0, m_nScreenHeigth, 0.5f, 1000.0f);
	m_matProjection = matrix::CenteredOrthogonal(m_nScreenWidth, m_nScreenHeigth, 0.1f, 100.0f);
	//m_matProjection = matrix();

	//Check for errors
	GLenum error = glGetError();
	if(error != GL_NO_ERROR)
	{
		SetConsoleColor(RED | INTENSE);
		cout << "An error ocurred while initializing OpenGL; ";
		cout << gluErrorString(error) << endl;
		SetConsoleColor();
		return false;
	}

	return true;
}
bool Renderer::LoadShaders()
{
	if(!m_shaderColor.Initialize())
		return false;
	if(!m_shaderTexture.Initialize())
		return false;
	return true;
}
bool Renderer::LoadStdModels()
{
	if(!m_modelScreenQuad.Create(unitQuadPoints, NULL, unitQuadTexCoords, unitQuadElementCount))
		return false;
	if(!m_modelScreenQuadLines.Create(unitQuadLines, NULL, NULL, unitQuadLinesElementCount))
		return false;
	return true;
}
bool Renderer::LoadFonts()
{
	//Loading Default Font
	stringstream path;
	path << GetWorkingDir() << "\\" << PATH_FONTDIR << FILE_FONTDEFAULT;
	string path2 = path.str();
	m_pDefFont = TTF_OpenFont(path2.c_str(), FONTPTS_TINY);

	if(m_pDefFont == NULL)
	{
		Error(cout << "Failed to load font -> " << TTF_GetError() << " @ " << path.str().c_str());
		return false;
	}
	cout << "Default Font loaded @ "; Path(path.str().c_str());
	return true;
}
void Renderer::Clear()
{
	//Set clear color
	glClearColor(color::Black);
	//Clear color buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void Renderer::Present()
{
	SDL_GL_SwapWindow(m_pScreen);
}
void Renderer::Cleanup()

{
	if(m_pDefFont != NULL)
	{
		TTF_CloseFont(m_pDefFont);
		m_pDefFont = NULL;
	}

	//Cleanup SDL_TTF
	TTF_Quit();

	if(m_pGLContext != NULL) {
		SDL_GL_DeleteContext(m_pGLContext);
		m_pGLContext = NULL;
	}
	if(m_pScreen != NULL) {
		SDL_DestroyWindow(m_pScreen);
		m_pScreen = NULL;
	}

	cout << "Render cleanup done" << endl;
}
void Renderer::SetBillboarding(bool enable)
{
	m_bBillboarding = enable;
}
void Renderer::DrawLine(const vector3 &pointA, const vector3 &pointB, float pointSize)
{
	if(m_pCamera)
	{
		m_shaderColor.Bind();
		m_shaderColor.SetColor(m_gradientColors[0]);
		m_shaderColor.SetSize(vector2(1.0f, 1.0f));
		m_shaderColor.SetProjection(m_pCamera->GetProjection());
		m_shaderColor.SetView(m_pCamera->GetViewMatrix());
		m_shaderColor.SetWorld(matrix());

		glPointSize(pointSize);

		int vertexAttrib = m_shaderColor.GetVertexLocation();

		glBegin(GL_LINES);
		glVertexAttrib3f(vertexAttrib, pointA.x, pointA.y, pointA.z);
		glVertexAttrib3f(vertexAttrib, pointB.x, pointB.y, pointB.z);
		glEnd();
	}
}
void Renderer::BeginUniformLine(float pointSize)
{
	m_shaderColor.Bind();
	m_shaderColor.SetColor(m_gradientColors[0]);
	m_shaderColor.SetSize(vector2(1.0f, 1.0f));
	m_shaderColor.SetProjection(m_pCamera->GetProjection());
	m_shaderColor.SetView(m_pCamera->GetViewMatrix());
	m_shaderColor.SetWorld(matrix());
	glPointSize(pointSize);
	m_lineVertPos = m_shaderColor.GetVertexLocation();
}
void Renderer::DrawUniformLine(const vector3 &pointA, const vector3 &pointB)
{
	glBegin(GL_LINES);
	glVertexAttrib3f(m_lineVertPos, pointA.x, pointA.y, pointA.z);
	glVertexAttrib3f(m_lineVertPos, pointB.x, pointB.y, pointB.z);
	glEnd();
}
void Renderer::EndUniformLine()
{
	m_shaderColor.Unbind();
}
void Renderer::DrawRect2D(const vector2 &pos, const vector2 &size)
{
	_drawRect(vector3(pos.x, pos.y, 0.0f), size, vector3(), vector2(), m_matProjection, matrix());
}
void Renderer::DrawRect2D(const vector2 &pos, const vector2 &size, float rotation, const vector2 &rotationOrigin)
{
	_drawRect(vector3(pos.x, pos.y, 0.0f), size, vector3(0.0f, 0.0f, rotation), rotationOrigin, m_matProjection, matrix());
}
void Renderer::SetFillMode(const FillMode &fm)
{
	m_fillMode = fm;
}
void Renderer::SetColor(const color &c)
{
	m_gradientColors[0] = c;
}
void Renderer::SetColor(const uint &id, const color &c)
{
	if(id > m_gradientColors.size())
	{
		Error(cout << "Gradient color out of range");
		return;
	}
	m_gradientColors[id] = c;
}
bool Renderer::SetGradientLength(const uint &length)
{
	if(length < 1)
		return false;
	m_gradientColors.resize(length);
	return true;
}
uint Renderer::GetGradientLength()
{
	return m_gradientColors.size();
}
vector2 Renderer::GetScreenSize()
{
	return vector2(m_nScreenWidth , m_nScreenHeigth);
}
void Renderer::SetTexture(Texture *pTexture)
{
	m_pDrawTexture = pTexture;
}
int Renderer::PrintStr2D(const vector2 &pos, const string &text, const color &c)
{
	//Split string in multiple lines
	vector<string> lineStrings;
	int lastLineEnd = 0;
	int lineCount = 0;
	int yLineSplit = FONTPTS_SMALL + 2;
	for(int i = 0; i < text.length(); i++)
	{
		if(text[i] == '\n')
		{
			//single \n character
			if(lastLineEnd != i)
				DrawTextLine(vector2(pos.x, pos.y + yLineSplit * lineCount), text.substr(lastLineEnd, i - lastLineEnd), c);
			lastLineEnd = i+1;
			lineCount++;
		}
	}
	DrawTextLine(vector2(pos.x, pos.y + yLineSplit * lineCount), text.substr(lastLineEnd, text.length() - lastLineEnd), c);
	lineCount++;

	return yLineSplit * lineCount;
}
void Renderer::DrawTextLine(const vector2 &pos, const string &text, const color &c)
{
	if(text.empty())
		return;

	SDL_Color fg;
	fg.r = c.r;
	fg.g = c.g;
	fg.b = c.b;
	fg.unused = c.a;

	Surface textSurface = Surface(TTF_RenderText_Blended(m_pDefFont, text.c_str(), fg));
	if(!textSurface.IsValidSurface())
	{
		RError(cout << "Failed to create text surface");
		return;
	}

	Texture textTexture(&textSurface);

	SetTexture(&textTexture);
	SetFillMode(FM_Texture);
	SetColor(color::White);
	DrawRect2D(pos, textTexture.GetTextureDimensionsVector(), 0.0f, vector2());
}
matrix Renderer::GetProjectionMatrix()
{
	return m_matProjection;
}
SDL_Window *Renderer::GetScreenSurface()
{
	return m_pScreen;
}
boost::shared_ptr<ICamera> Renderer::GetActiveCamera()
{
	return m_pCamera;
}
void Renderer::SetActiveCamera(boost::shared_ptr<ICamera> pCamera)
{
	m_pCamera = pCamera;
}
void Renderer::DrawRect3D(const vector3 &pos, const vector2 &size, const vector3 &rotation, const vector2 &rotationOrigin)
{
	if(m_pCamera)
	{
		_drawRect(pos, size, rotation, rotationOrigin, m_pCamera->GetProjection(), m_pCamera->GetViewMatrix());
	}
}
void Renderer::_drawRect(const vector3 &pos, const vector2 &size, const vector3 &rotation, const vector2 &rotationOrigin,
	const matrix &projection, const matrix &view)
{
	matrix screenOriginTrans = matrix::Translation( vector3(-rotationOrigin.x, -rotationOrigin.y, 0.0f));
	matrix screenTrans = matrix::Translation( vector3(pos.x + rotationOrigin.x, pos.y + rotationOrigin.y, pos.z) );
	matrix screenRot = matrix::Rotation(rotation);
	//Combined object Transformation
	matrix world;

	// Rotate to camera
	if(m_bBillboarding && m_pCamera) {
		//world = m_pCamera->GetBillboardMatrix() * screenOriginTrans;
		world = m_pCamera->GetBillboardMatrix() * screenRot * screenOriginTrans;
	} else {
		world = screenRot * screenOriginTrans;
	}

	//Translate to target position + rotation origin
	world = screenTrans * world;

	//Draw a quad on the screen
	if(m_fillMode == FM_Solid) {
		m_shaderColor.Bind();
		m_shaderColor.SetProjection(projection);
		m_shaderColor.SetSize(size);
		m_shaderColor.SetWorld(world);
		m_shaderColor.SetView(view);
		m_shaderColor.SetColor(m_gradientColors[0]);
		m_modelScreenQuad.SetCustomPointers(m_shaderColor.GetVertexLocation(), 0, 0);
		m_modelScreenQuad.CustomBind();
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDrawArrays(GL_TRIANGLE_STRIP, 0, m_modelScreenQuad.GetElementCount());
	} else if(m_fillMode == FM_Border) {
		m_shaderColor.Bind();
		m_shaderColor.SetProjection(projection);
		m_shaderColor.SetSize(size);
		m_shaderColor.SetWorld(world);
		m_shaderColor.SetView(view);
		m_shaderColor.SetColor(m_gradientColors[0]);
		m_modelScreenQuadLines.SetCustomPointers(m_shaderColor.GetVertexLocation(), 0, 0);
		m_modelScreenQuadLines.CustomBind();
		glDrawArrays(GL_LINE_STRIP, 0, m_modelScreenQuadLines.GetElementCount());
	} else if(m_fillMode == FM_GradientH) {
		RError(cout << "FM_GradientH not yet implemented");
	} else if(m_fillMode == FM_GradientV) {
		RError(cout << "FM_GradientV not yet implemented");
	} else if(m_fillMode == FM_Texture) {
		if(m_pDrawTexture == NULL) {
			RError(cout << "Texture pointer is invalid, FM_Texture failed to render rectangle");
			glPopMatrix();
			return;
		}
		if(!m_pDrawTexture->IsValidTexture())
		{
			RError(cout << "Texture is invalid [" << m_pDrawTexture->GetValidator() << "], FM_Texture failed to render rectangle");
			glPopMatrix();
			return;
		}

		m_shaderTexture.Bind();
		m_shaderTexture.SetSize(size);
		m_shaderTexture.SetWorld(world);
		m_shaderTexture.SetView(view);
		m_shaderTexture.SetProjection(projection);
		m_shaderTexture.SetTexture(m_pDrawTexture);
		m_modelScreenQuad.SetCustomPointers(m_shaderTexture.GetVertexLocation(), 0, m_shaderTexture.GetTexcoordLocation());
		m_modelScreenQuad.CustomBind();
		glDrawArrays(GL_TRIANGLE_STRIP, 0, m_modelScreenQuad.GetElementCount());
	}

	//Post Error-check
	glErrorCheck();
}
int Renderer::PrintStr3D(const vector3 &pos, const vector3 &rotation, const string &text, const color &c)
{
	return 0;
}